package com.wnxy.sk.gateway.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnxy.sk.common.jwt.JwtTemplate;
import com.wnxy.sk.common.result.Result;
import com.wnxy.sk.common.result.enums.impl.ResultCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;

/**
 * 全局过滤器: GlobalFilter
 * 1、针对所有的路由配置都生效；
 * 2、在yml路由中配置的过滤器是局部过滤器，针对某一个微服务起作用
 */
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {

    @Autowired
    private JwtTemplate jwtTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    private final String USER_TOKEN = "user:token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 获取请求地址
        String path = request.getURI().getPath();
        // 判断请求地址中是否有包含 /auth 资源
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        boolean match = antPathMatcher.match("/**/auth/**", path);
        if (match) {
            // 当前路径需要进行鉴权，必须登陆后才能访问. 所以这里先获取token
            String token = request.getHeaders().getFirst("Authorization");
            if (StringUtils.isEmpty(token)) {
                log.info("token为空！");
                // 鉴权失败，返回错误信息
                return error(response);
            }
            // 因为请求头携带token的格式是 Bearer token, 所以这里需要去掉前缀
            token = token.replace("Bearer ","");

            // 校验token
            boolean verify = jwtTemplate.verify(token);
            if (!verify) {
                log.info("token校验失败，格式错误！");
                return error(response);
            }

            // 解析token，获取token中的用户id
            String userId = (String) jwtTemplate.parseToken(token, "userId");

            // 校验token：根据请求头中的token查看Redis中是否存在。  user:token:id  AABBCC
            String key = USER_TOKEN+":"+userId;
            String redisToken =
                    (String) redisTemplate.opsForValue().get(key);
            if (StringUtils.isEmpty(redisToken)) {
                log.info("token无效！");
                return error(response);
            }

            // 把解析到的userId传递给微服务，这里需要重写请求头
            request.mutate().header("userId",userId);

            // 更新token的有效时间
            redisTemplate.expire(key, Duration.ofMinutes(30));
        }

        // 放行
        return chain.filter(exchange);
    }

    // 返回错误
    @SneakyThrows  // 抑制编译器编译时期异常
    private Mono<Void> error(ServerHttpResponse response) {
        // 错误返回结果
        Result<Object> result = Result.fail(ResultCode.PERMISSION_NO_ACCESS);
        // 对象转换未json字符串，再转换为byte数组
        byte[] bytes = new ObjectMapper().writeValueAsBytes(result);
        DataBuffer dataBuffer=response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(dataBuffer));
    }

    // 作用：控制过滤器执行顺序，数字越小越先执行
    @Override
    public int getOrder() {
        return 0;
    }
}
